<template>
  <div class="detailBox">
    <TopUtils 
      :name="state.title" 
      :id="ID" 
      :isCopy="isCopy" 
      @generatePicture="saveScene" 
      @refresh="refreshTopo"
      @saveScene="saveScene(true)" 
      v-if="!isSceneCreate" 
    />
    
    <div class="screen_content tactical-content">
      <!-- 左侧设备区域 -->
      <el-icon class="leftIcon" @click="refreshScene" :class="{ 'rightIcon': !showLeft }">
        <div class="righticonimg" v-if="!showLeft"></div>
        <div class="lefticonimg" v-else></div>
      </el-icon>
      <div
        class="left scenes_left"
        :style="{ display: showLeft ? 'block' : 'none' }"
      >
        <el-tabs v-model="activeTab" @tab-click="handleTabClick" style="margin: 5px">
            <el-tab-pane label="靶标资源" name="component">
              <div class="resource-pane">
                <div class="group-bar">
                  <div v-for="(item, index) in radioGroup" :key="item.key" class="group-btn"
                    :class="{ active: index === designIndex }" @click="changeRadio(item.key, index)"
                    :title="item.nickname">
                    {{ item.nickname }}
                  </div>
                </div>
                <div class="search-card">
                  <el-input v-model="searchWord" placeholder="搜索资源名称..." clearable @input="searchFunc" 
                    :prefix-icon="Search" class="custom-search-input compact-search-input" />
                </div>
                <el-collapse v-model="activeNames" class="resource-collapse" @change="handleCollapseChange">
                  <el-collapse-item v-for="group in filteredResourceList" :key="group.name"
                    :title="`${group.name}（${group.fileList.length}）`" :name="group.name" class="custom-collapse-item">
                    <div class="resource-list">
                      <div v-for="(itemChild, idx) in group.fileList" :key="itemChild.name + idx" class="resource-card"
                        draggable="true" @dragstart="onDragStart(itemChild, $event)">
                        <el-tooltip effect="dark" :content="itemChild.name" placement="top-start">
                          <div>
                            <div class="icon-bg">
                              <img :src="itemChild.path" class="resource-icon" />
                            </div>

                            <div class="resource-name">
                              {{ itemChild.name }}
                            </div>
                          </div>
                        </el-tooltip>
                      </div>
                    </div>
                  </el-collapse-item>
                </el-collapse>
              </div>
              <div class="screen_content-bottom">
                <div style="width: 100%; height: 100%" ref="leftMain"></div>
              </div>
            </el-tab-pane>
            <el-tab-pane label="网络资源" name="network">
              <div class="resource-pane">
                <div class="search-card">
                  <el-input v-model="searchWord" placeholder="搜索资源名称..." clearable @input="searchFunc" 
                    :prefix-icon="Search" class="custom-search-input compact-search-input" />
                </div>
                <el-collapse v-model="activeNames" class="resource-collapse resource-collapse-other"
                  @change="handleCollapseChange">
                  <el-collapse-item v-for="group in filteredResourceList" :key="group.name"
                    :title="`${group.name}（${group.fileList.length}）`" :name="group.name" class="custom-collapse-item">
                    <div class="resource-list">
                      <div v-for="(itemChild, idx) in group.fileList" :key="itemChild.name + idx" class="resource-card"
                        draggable="true" @dragstart="onDragStart(itemChild, $event)">
                        <el-tooltip effect="dark" :content="itemChild.name" placement="top-start">
                          <div>
                            <div class="icon-bg">
                              <img :src="itemChild.path" class="resource-icon" />
                            </div>

                            <div class="resource-name">
                              {{ itemChild.name }}
                            </div>
                          </div>
                        </el-tooltip>
                      </div>
                    </div>
                  </el-collapse-item>
                </el-collapse>
              </div>
              <div class="screen_content-bottom">
                <div style="width: 100%; height: 100%" ref="netMain"></div>
              </div>
            </el-tab-pane>
            <el-tab-pane label="内置图形" name="builtIn">
              <div class="resource-pane">
                <el-collapse v-model="activeNames" class="resource-collapse resource-collapse-other"
                  @change="handleCollapseChange">
                  <el-collapse-item v-for="group in builtInResourceList" :key="group.name"
                    :title="`${group.name}（${group.fileList.length}）`" :name="group.name" class="custom-collapse-item">
                    <div class="resource-list">
                      <div v-for="(itemChild, idx) in group.fileList" :key="itemChild.name + idx" class="resource-card"
                        draggable="true" @dragstart="onDragStart(itemChild, $event)">
                        <div class="icon-bg">
                          <span v-if="itemChild.options.attrs?.text?.text === '文字'">{{ itemChild.options.attrs?.text?.text
                            }}</span>
                          <img :src="getImage(itemChild.type)" class="resource-icon" v-else />
                        </div>
                      </div>
                    </div>
                  </el-collapse-item>
                </el-collapse>
              </div>
              <div class="screen_content-bottom">
                <div style="width: 100%; height: 100%" ref="leftBuildMain"></div>
              </div>
            </el-tab-pane>
        </el-tabs>
      </div>

      <!-- 右侧节点信息面板，紧贴graphMain右侧 -->
      <div v-if="selectedNodeInfo" class="node-info-panel">
        <div class="panel-header">
          <el-tabs v-model="nodeInfoActiveTab" class="node-info-tabs">
            <el-tab-pane label="设备信息" name="info"></el-tab-pane>
            <el-tab-pane label="攻击路径" name="path"></el-tab-pane>
            <el-tab-pane label="战术详情" name="tactic"></el-tab-pane>
          </el-tabs>
          <el-button size="small" link @click="closeNodeInfoPanel">
            <el-icon><Close /></el-icon>
          </el-button>
        </div>
        <div class="panel-content">
          <template v-if="nodeInfoActiveTab === 'info'">
            <div class="info-item"><label>节点名称:</label><span>{{ selectedNodeInfo.name || '未命名' }}</span></div>
            <div class="info-item"><label>节点ID:</label><span>{{ selectedNodeInfo.id }}</span></div>
            <div class="info-item"><label>节点类型:</label><span>{{ selectedNodeInfo.shape || '未知' }}</span></div>
            <div class="info-item"><label>位置:</label><span>X: {{ selectedNodeInfo.position?.x || 0 }}, Y: {{ selectedNodeInfo.position?.y || 0 }}</span></div>
            <div class="info-item"><label>大小:</label><span>宽: {{ selectedNodeInfo.size?.width || 0 }}, 高: {{ selectedNodeInfo.size?.height || 0 }}</span></div>
            <div v-if="selectedNodeInfo.attrs?.image?.options" class="info-item"><label>组件类型:</label><span>{{ selectedNodeInfo.attrs.image.options.kindName || '未知' }}</span></div>
            <div v-if="selectedNodeInfo.attrs?.image?.options?.category" class="info-item"><label>分类:</label><span>{{ getCategoryName(selectedNodeInfo.attrs.image.options.category) }}</span></div>
            <div v-if="selectedNodeInfo.options" class="info-item"><label>组件类型:</label><span>{{ selectedNodeInfo.options.kindName || '未知' }}</span></div>
            <div v-if="selectedNodeInfo.options?.category" class="info-item"><label>分类:</label><span>{{ getCategoryName(selectedNodeInfo.options.category) }}</span></div>
            
            <!-- 漏洞树显示 -->
            <div v-if="isTargetNode" class="vuln-tree-section">
              <div class="vuln-tree-title">
                <span>漏洞树分析</span>
                <el-button v-if="selectedVulnPath.length > 0" size="small" type="primary" @click="generateAttackPath">
                  生成攻击路径
                </el-button>
              </div>
              
              <!-- 已选择的漏洞路径 -->
              <div v-if="selectedVulnPath.length > 0" class="selected-path">
                <div class="path-label">已选择路径:</div>
                <div class="path-items">
                  <template v-for="(item, index) in selectedVulnPath" :key="index">
                    <el-tag 
                      type="success"
                      size="small"
                      closable
                      @close="removeVulnPathItem(index)"
                    >
                      {{ item }}
                    </el-tag>
                    <span v-if="index < selectedVulnPath.length - 1" class="path-separator">></span>
                  </template>
                </div>
              </div>
              
              <!-- 当前层级的漏洞选项 -->
              <div class="vuln-options">
                <div 
                  v-for="(vuln, key) in currentVulnOptions" 
                  :key="key"
                  class="vuln-option"
                  @click="selectVulnOption(key)"
                >
                  <el-icon><i class="el-icon-arrow-right" /></el-icon>
                  <span>{{ key }}</span>
                  <el-icon v-if="hasChildren(vuln)" class="expand-icon"><i class="el-icon-arrow-down" /></el-icon>
                </div>
              </div>
            </div>
            
            <!-- <div v-else class="vuln-list-title"><el-icon style="color:#4ade80"><i class="el-icon-warning-outline" /></el-icon> 漏洞列表</div>
            <div v-for="vuln in mockVulnList" :key="vuln.id" class="vuln-card">
              <div class="vuln-header">
                <span class="vuln-title">{{ vuln.title }}</span>
                <span class="vuln-protocol">{{ vuln.protocol }}</span>
              </div>
              <div class="vuln-desc">{{ vuln.desc }}</div>
              <div class="vuln-attack">ATT&CK: <span class="vuln-attack-id">{{ vuln.attack }}</span></div>
              <div class="vuln-rate">{{ vuln.rate }}%</div>
            </div> -->
          </template>
          <template v-else-if="nodeInfoActiveTab === 'path'">
            <div class="path-header">
              <span>攻击路径详情</span>
              <span class="path-success">预估成功率: <b style="color:#4ade80">{{ mockPath.success }}%</b></span>
            </div>
            <div v-if="selectedNodeInfo" class="path-filter">
              <el-tag type="info" closable @close="clearNodeFilter">
                当前筛选: {{ selectedNodeInfo.name }}
              </el-tag>
            </div>
            <div v-for="(step, idx) in filteredPathSteps" :key="idx" class="path-step" :class="{ 'detailed-path': step.isDetailedPath }">
              <div class="path-step-title">
                <el-icon><i class="el-icon-aim" /></el-icon>
                {{ step.from }} → {{ step.to }}
                <el-tag v-if="step.isDetailedPath" type="danger" size="small" style="margin-left: 8px;">详细路径</el-tag>
              </div>
              <div v-if="step.intermediateSteps && step.intermediateSteps.length > 0" class="path-intermediate">
                <div class="intermediate-label">攻击路径:</div>
                <div class="intermediate-steps">
                  <span class="path-item">{{ step.from }}</span>
                  <span class="path-separator">></span>
                  <template v-for="(intermediate, i) in step.intermediateSteps" :key="i">
                    <el-tag 
                      type="danger"
                      size="small"
                      class="path-item"
                    >
                      {{ intermediate }}
                    </el-tag>
                    <span v-if="i < step.intermediateSteps.length - 1" class="path-separator">></span>
                  </template>
                  <span class="path-separator">></span>
                  <span class="path-item">{{ step.to }}</span>
                </div>
              </div>
              <div class="path-step-attack">ATT&CK: <span>{{ step.attack }}</span></div>
              <div class="path-step-rate">成功率: <b>{{ step.rate }}%</b></div>
            </div>
            <div v-if="filteredPathSteps.length === 0" class="path-empty">
              <el-empty description="暂无相关攻击路径" />
            </div>
            <div v-else class="path-footer">攻击路径已验证，可生成战术方案</div>
          </template>
          <template v-else-if="nodeInfoActiveTab === 'tactic'">
            <div class="tactic-header">
              <span>攻击战术方案</span>
              <span class="tactic-success">整体成功率: <b style="color:#4ade80">{{ mockTactic.success }}%</b></span>
            </div>
            <div v-if="mockTactic.path" class="tactic-info-item"><label>攻击路径:</label><span>{{ mockTactic.path }}</span></div>
            <div v-else class="tactic-info-item"><label>攻击路径:</label><span style="color: #999;">暂无攻击路径</span></div>
            <div v-if="mockTactic.target" class="tactic-info-item"><label>攻击目标:</label><span>{{ mockTactic.target }}</span></div>
            <div v-else class="tactic-info-item"><label>攻击目标:</label><span style="color: #999;">暂无攻击目标</span></div>
            <div v-if="mockTactic.scope" class="tactic-info-item"><label>影响范围:</label><span>{{ mockTactic.scope }}</span></div>
            <div v-else class="tactic-info-item"><label>影响范围:</label><span style="color: #999;">暂无影响范围</span></div>
            <div v-if="mockTactic.steps.length > 0">
              <div v-for="(step, idx) in mockTactic.steps" :key="idx" class="tactic-step">
                <div class="tactic-step-num">{{ idx+1 }}</div>
                <div class="tactic-step-content">
                  <div class="tactic-step-title">{{ step.title }}</div>
                  <div class="tactic-step-desc">{{ step.desc }}</div>
                  <div class="tactic-step-attack">ATT&CK: <span>{{ step.attack }}</span></div>
                </div>
              </div>
            </div>
            <div v-else class="tactic-empty">
              <el-empty description="暂无战术步骤，请先生成攻击路径" />
            </div>
          </template>
        </div>
      </div>

      <div class="main tactical-main" :class="{ 'scene-fold': showLeft, 'scene-no-fold': !showLeft }">
        <div 
          ref="graphMain" 
          class="graphMain" 
          @drop="onDrop" 
          @dragover.prevent
          @dragenter.prevent
          @dragleave.prevent
        ></div>
      </div>
      
      <ContextMenu />
      <Detail 
        ref="nodeDetail" 
        :drawer="drawer" 
        :info="nodeInfo" 
        :id="ID" 
        :clusId="clusterID" 
        @editNode="editNodeDetail"
        @closeDialog="closeFunc" 
        @refreshGraph="refreshGraph"
      />
      <ToolExecute 
        ref="nodeDetail" 
        :toolExecuteDrawer="toolExecuteDrawer" 
        :info="nodeInfo" 
        :id="ID" 
        :clusId="clusterID"
        @closeDialog="closeToolExecuteFunc" 
        @refreshGraph="refreshGraph"
      />
      <TrafficReplay 
        ref="nodeDetail" 
        :trafficDrawer="trafficDrawer" 
        :info="nodeInfo" 
        :id="ID" 
        :clusId="clusterID"
        @closeDialog="closeTrafficFunc" 
        @refreshGraph="refreshGraph"
      />
      <Virtual 
        ref="virtualComponent" 
        type="scene" 
        :dialogList="dialogList" 
        @closeDialog="closeVirtualFunc"
        @topDialog="topDialog"
      />
      <Docker 
        ref="dockerComponent" 
        :dialogList="dockerDialogList" 
        @topDialog="topDockerDialog"
        @closeDialog="closeDockerFunc"
      />
      <DockerLog 
        ref="dockerLogComponent" 
        :dialogList="dockerLogList" 
        @topDialog="topDockerLogDialog"
        @closeDialog="closeDockerLogFunc"
      />
      <Snapshot :dialogList="dialogList" />
      <SnapshotScene :dialogList="dialogList" />
      <AttackPath />
      
      <el-dialog v-model="materialInterfaceShow" title="虚实互联配置" width="500" @close="unmatchMaterialInterface()">
        <div>
          <el-form label-position="right" label-width="160px" class="mx-2 mt-1">
            <el-form-item label="选择实物设备端口:">
              <el-select filterable clearable v-model="materialInterfaceID" @change="changeMaterialInterface">
                <el-option v-for="item in materialInterfaceList" :key="item.ID" :label="item.name"
                  :value="item.ID"></el-option>
              </el-select>
            </el-form-item>
            <el-form-item label="输入虚拟机IP:">
              <el-input v-model="materialInterfaceConVirIP" clearable placeholder="输入虚拟机端口IP" />
            </el-form-item>
            <div v-if="choiceMaterialType === 2">
              <el-form-item label="输入虚拟机端口网关:">
                <el-input v-model="materialInterfaceConVirWg" clearable placeholder="输入虚拟机端口网关" />
              </el-form-item>
            </div>
          </el-form>
        </div>

        <template #footer>
          <div class="dialog-footer">
            <el-button @click="unmatchMaterialInterface()">取消</el-button>
            <el-button type="primary" @click="matchMaterialInterface()">
              确认
            </el-button>
          </div>
        </template>
      </el-dialog>

      <!-- 连线信息弹框 -->
      <el-dialog v-model="connectionInfoShow" title="连线信息" width="600" @close="closeConnectionInfo">
        <div class="connection-info">
          <div class="connection-header">
            <el-icon style="color: #4ade80; margin-right: 8px;"><i class="el-icon-connection" /></el-icon>
            <span>{{ connectionInfo.operationType || '连线操作' }}成功</span>
          </div>
          
          <div class="connection-details">
            <div class="node-info-section">
              <h4>起始节点</h4>
              <div class="node-card">
                <div class="node-icon" :style="{ backgroundColor: connectionInfo.source?.color || '#4285f4' }">
                  <i class="el-icon-cpu" />
                </div>
                <div class="node-details">
                  <div class="node-name">{{ connectionInfo.source?.name || '未知节点' }}</div>
                  <div class="node-type">{{ connectionInfo.source?.type || '未知类型' }}</div>
                  <div class="node-id">ID: {{ connectionInfo.source?.id || '未知' }}</div>
                </div>
              </div>
            </div>
            
            <div class="connection-arrow">
              <el-icon style="color: #4ade80; font-size: 24px;"><i class="el-icon-arrow-right" /></el-icon>
            </div>
            
            <div class="node-info-section">
              <h4>目标节点</h4>
              <div class="node-card">
                <div class="node-icon" :style="{ backgroundColor: connectionInfo.target?.color || '#66ccaa' }">
                  <i class="el-icon-cpu" />
                </div>
                <div class="node-details">
                  <div class="node-name">{{ connectionInfo.target?.name || '未知节点' }}</div>
                  <div class="node-type">{{ connectionInfo.target?.type || '未知类型' }}</div>
                  <div class="node-id">ID: {{ connectionInfo.target?.id || '未知' }}</div>
                </div>
              </div>
            </div>
          </div>
          
          <div class="connection-meta">
            <div class="meta-item">
              <label>操作类型:</label>
              <span>{{ connectionInfo.operationType || '未知' }}</span>
            </div>
            <div class="meta-item">
              <label>连线ID:</label>
              <span>{{ connectionInfo.edgeId || '未知' }}</span>
            </div>
            <div class="meta-item">
              <label>操作时间:</label>
              <span>{{ connectionInfo.createTime || '未知' }}</span>
            </div>
          </div>
        </div>

        <template #footer>
          <div class="dialog-footer">
            <el-button @click="closeConnectionInfo">关闭</el-button>
            <el-button type="primary" @click="viewConnectionDetails">查看详情</el-button>
          </div>
        </template>
      </el-dialog>
    </div>
  </div>
</template>

<script lang="ts" setup>
import {
  onMounted,
  onUnmounted,
  reactive,
  ref,
  computed,
  watch,
} from 'vue';
import { DocumentCopy, Search, Close } from '@element-plus/icons-vue';
import eventBus from 'vue3-eventbus';
import type { Graph } from '@antv/x6';
import { sceneBuiltBaseNode } from './service/config';
import { useRouter } from 'vue-router';
import Detail from './children/detail.vue';
import TopUtils from './children/topUtils.vue';
import ContextMenu from './children/contextMenu.vue';
import Snapshot from './children/snapshot.vue';
import SnapshotScene from './children/snapshotScene.vue';
import AttackPath from './children/attackPath.vue';
import Virtual from '@/components/console/virtual.vue';
import Docker from '@/components/console/docker.vue';
import DockerLog from '@/components/console/dockerLog.vue';
import { simulationCover } from './defaultConfig';
import {
  initGraph,
  initNode,
  initStencil,
} from './service/initScene';
import {
  getComponent,
  editorNode,
  editorLink,
  deleteLink,
  deleteNode,
  editorPutNode,
  editorPutLink,
  editSceneList,
  getVirtual,
  editScene,
  transferScene,
  getToolType,
  getToolList,
  uploadMyGraphicsFile,
  getByID,
  getMaterialLinkCheck,
  getInstanceLinkOption,
  getDescFunc,
  setDescFunc,
  getComponentType,
} from '@/api/topo/topo.js';
import { getVulnEnvironmentList } from '@/api/topo/vulnEnvironment';
import { dataURLtoFile, categoryMapList, getRandomIP } from '@/utils/common';
import { ElMessage } from 'element-plus/es';
import { emitter } from '@/utils/bus';
import {
  showContext,
  refreshGraph,
} from './service/vueService';
import { allCount } from '@/layout/notification/service/service';
import TrafficReplay from './children/trafficReplay.vue';
import ToolExecute from './children/debugTool.vue';
import { useGraphStore } from '@/pinia/modules/graph';
import { defaultSceneTopo, defaultComponentType } from './defaultRes';

// ================== 变量定义 - activeTab必须在最前面 ==================
const activeTab = ref('component');

// tabActiveNames必须在activeTab之后定义
const tabActiveNames = reactive({
  component: [],
  network: [],
  builtIn: [],
  tool: [],
});

// activeNames也必须在activeTab之后定义
const activeNames = ref([]);

// searchWord在computed中被使用，必须在其定义之前
const searchWord = ref('');

// radioGroup在模板中被使用，必须在其定义之前
const radioGroup = ref([]);

// showLeft在模板中被使用，必须在其定义之前
const showLeft = ref(true);

// designIndex在模板中被使用，必须在其定义之前
const designIndex = ref(-1);
const designIndexLast = ref(-1);

// selectedNodeInfo在模板中被使用，必须在其定义之前
const selectedNodeInfo = ref(null);

// nodeInfoActiveTab在模板中被使用，必须在其定义之前
const nodeInfoActiveTab = ref('info');

// NAME在state中被使用，必须在其定义之前
const router = useRouter();
const ID = ref(null);
ID.value = router.currentRoute.value.query.id || 'test-scene-001'; // 添加默认测试ID
const NAME = router.currentRoute.value.query.name || '创建战术';

// state在computed中被使用，必须在其定义之前
const state = reactive({
  // 拓扑名称
  title: NAME,
});

// clusterID在模板中被使用，必须在其定义之前
let clusterID = ref(0);

// isCopy在模板中被使用，必须在其定义之前
let isCopy = ref('');

// materialInterfaceShow在模板中被使用，必须在其定义之前
const materialInterfaceShow = ref(false);

// connectionInfoShow在模板中被使用，必须在其定义之前
const connectionInfoShow = ref(false);

// connectionInfo在模板中被使用，必须在其定义之前
const connectionInfo = ref({
  source: null,
  target: null,
  edgeId: '',
  createTime: ''
});

const graphStore = useGraphStore();
let sceneDetail = ref({});

// 测试数据 - 当没有传入参数时使用
const testSceneData = {
  ID: 'test-scene-001',
  name: '测试战术场景',
  clusterID: 1,
  isCopy: '',
  nodes: [
    {
      id: 'kali-host',
      shape: 'org-node',
      x: 100,
      y: 100,
      width: 80,
      height: 80,
      attrs: {
        text: {
          text: 'Kali主机',
          fontSize: 12,
          textWrap: {
            text: 'Kali主机',
            width: 1,
            ellipsis: true,
            breakWord: true,
          },
        },
        image: {
          'xlink:href': 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.2.96/svg/desktop-tower-monitor.svg',
          options: {
            kindName: 'Kali主机',
            category: 8
          },
        },
        '.rank': {
          fill: 'transparent',
        },
        '.image': {
          refHeight2: '90%',
          refWidth2: '90%',
          refX: '20%',
          refY: '25%',
          x: 0,
          xAlign: 'middle',
          y: -7,
          yAlign: 'middle',
        },
        '.name': {
          fill: '#5b6775',
          fontFamily: 'Arial',
          refX: '20%',
          refY: '70%',
          xAlign: 'middle',
          yAlign: 'top',
          fontSize: 12,
          textWrap: {
            width: 66,
          },
          textVerticalAnchor: 'middle',
        },
        '.rankCenter': {
          fill: '#ff0000',
          height: 20,
          width: 20,
          xAlign: 'middle',
          refX: '50%',
          yAlign: 'top',
          refY: '0%',
          'xlink:href': ``,
        },
      },
      ports: {
        groups: {
          in: {
            position: 'left',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          out: {
            position: 'right',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
        },
        items: [
          { group: 'in', id: 'in-1' },
          { group: 'out', id: 'out-1' },
          { group: 'top', id: 'top-1' },
          { group: 'bottom', id: 'bottom-1' },
        ],
      },
      options: {
        kindName: 'Kali主机',
        category: 8
      }
    },
    {
      id: 'fusion-terminal',
      shape: 'org-node',
      x: 300,
      y: 100,
      width: 80,
      height: 80,
      attrs: {
        text: {
          text: '智能融合终端',
          fontSize: 12,
          textWrap: {
            text: '智能融合终端',
            width: 1,
            ellipsis: true,
            breakWord: true,
          },
        },
        image: {
          'xlink:href': 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.2.96/svg/monitor-cellphone.svg',
          options: {
            kindName: '智能融合终端',
            category: 3
          },
        },
        '.rank': {
          fill: 'transparent',
        },
        '.image': {
          refHeight2: '90%',
          refWidth2: '90%',
          refX: '20%',
          refY: '25%',
          x: 0,
          xAlign: 'middle',
          y: -7,
          yAlign: 'middle',
        },
        '.name': {
          fill: '#5b6775',
          fontFamily: 'Arial',
          refX: '20%',
          refY: '70%',
          xAlign: 'middle',
          yAlign: 'top',
          fontSize: 12,
          textWrap: {
            width: 66,
          },
          textVerticalAnchor: 'middle',
        },
        '.rankCenter': {
          fill: '#ff0000',
          height: 20,
          width: 20,
          xAlign: 'middle',
          refX: '50%',
          yAlign: 'top',
          refY: '0%',
          'xlink:href': ``,
        },
      },
      ports: {
        groups: {
          in: {
            position: 'left',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          out: {
            position: 'right',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
        },
        items: [
          { group: 'in', id: 'in-1' },
          { group: 'out', id: 'out-1' },
          { group: 'top', id: 'top-1' },
          { group: 'bottom', id: 'bottom-1' },
        ],
      },
      options: {
        kindName: '智能融合终端',
        category: 3
      }
    },
    {
      id: 'circuit-breaker',
      shape: 'org-node',
      x: 500,
      y: 100,
      width: 80,
      height: 80,
      attrs: {
        text: {
          text: '智能断路器',
          fontSize: 12,
          textWrap: {
            text: '智能断路器',
            width: 1,
            ellipsis: true,
            breakWord: true,
          },
        },
        image: {
          'xlink:href': 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.2.96/svg/electric-switch.svg',
          options: {
            kindName: '智能断路器',
            category: 3
          },
        },
        '.rank': {
          fill: 'transparent',
        },
        '.image': {
          refHeight2: '90%',
          refWidth2: '90%',
          refX: '20%',
          refY: '25%',
          x: 0,
          xAlign: 'middle',
          y: -7,
          yAlign: 'middle',
        },
        '.name': {
          fill: '#5b6775',
          fontFamily: 'Arial',
          refX: '20%',
          refY: '70%',
          xAlign: 'middle',
          yAlign: 'top',
          fontSize: 12,
          textWrap: {
            width: 66,
          },
          textVerticalAnchor: 'middle',
        },
        '.rankCenter': {
          fill: '#ff0000',
          height: 20,
          width: 20,
          xAlign: 'middle',
          refX: '50%',
          yAlign: 'top',
          refY: '0%',
          'xlink:href': ``,
        },
      },
      ports: {
        groups: {
          in: {
            position: 'left',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          out: {
            position: 'right',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
        },
        items: [
          { group: 'in', id: 'in-1' },
          { group: 'out', id: 'out-1' },
          { group: 'top', id: 'top-1' },
          { group: 'bottom', id: 'bottom-1' },
        ],
      },
      options: {
        kindName: '智能断路器',
        category: 3
      }
    },
    {
      id: 'transformer',
      shape: 'org-node',
      x: 700,
      y: 100,
      width: 80,
      height: 80,
      attrs: {
        text: {
          text: '配电变压器',
          fontSize: 12,
          textWrap: {
            text: '配电变压器',
            width: 1,
            ellipsis: true,
            breakWord: true,
          },
        },
        image: {
          'xlink:href': 'https://cdn.jsdelivr.net/npm/@mdi/svg@7.2.96/svg/transmission-tower.svg',
          options: {
            kindName: '配电变压器',
            category: 3
          },
        },
        '.rank': {
          fill: 'transparent',
        },
        '.image': {
          refHeight2: '90%',
          refWidth2: '90%',
          refX: '20%',
          refY: '25%',
          x: 0,
          xAlign: 'middle',
          y: -7,
          yAlign: 'middle',
        },
        '.name': {
          fill: '#5b6775',
          fontFamily: 'Arial',
          refX: '20%',
          refY: '70%',
          xAlign: 'middle',
          yAlign: 'top',
          fontSize: 12,
          textWrap: {
            width: 66,
          },
          textVerticalAnchor: 'middle',
        },
        '.rankCenter': {
          fill: '#ff0000',
          height: 20,
          width: 20,
          xAlign: 'middle',
          refX: '50%',
          yAlign: 'top',
          refY: '0%',
          'xlink:href': ``,
        },
      },
      ports: {
        groups: {
          in: {
            position: 'left',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          out: {
            position: 'right',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          top: {
            position: 'top',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
          bottom: {
            position: 'bottom',
            attrs: {
              circle: {
                r: 4,
                magnet: true,
                stroke: '#5F95FF',
                strokeWidth: 1,
                fill: '#fff',
                style: {
                  visibility: 'hidden',
                },
              },
            },
          },
        },
        items: [
          { group: 'in', id: 'in-1' },
          { group: 'out', id: 'out-1' },
          { group: 'top', id: 'top-1' },
          { group: 'bottom', id: 'bottom-1' },
        ],
      },
      options: {
        kindName: '配电变压器',
        category: 3
      }
    }
  ],
  edges: [
    {
      id: 'edge-1',
      source: { cell: 'kali-host', port: 'out-1' },
      target: { cell: 'fusion-terminal', port: 'in-1' },
      attrs: {
        line: {
          stroke: '#4ade80',
          strokeWidth: 4,
          targetMarker: {
            name: 'block',
            width: 12,
            height: 8
          }
        }
      }
    },
    {
      id: 'edge-2',
      source: { cell: 'fusion-terminal', port: 'out-1' },
      target: { cell: 'circuit-breaker', port: 'in-1' },
      attrs: {
        line: {
          stroke: '#4ade80',
          strokeWidth: 4,
          targetMarker: {
            name: 'block',
            width: 12,
            height: 8
          }
        }
      }
    },
    {
      id: 'edge-3',
      source: { cell: 'circuit-breaker', port: 'out-1' },
      target: { cell: 'transformer', port: 'in-1' },
      attrs: {
        line: {
          stroke: '#4ade80',
          strokeWidth: 4,
          targetMarker: {
            name: 'block',
            width: 12,
            height: 8
          }
        }
      }
    }
  ]
};

// 容器控制台
const dockerDialogList = ref([]);
const dockerComponent = ref(null);
// 容器日志
const dockerLogList = ref([]);
const dockerLogComponent = ref(null);
// 主画布实例
const graphMain = ref(null);
// 保存拓扑实例
let graph: Graph = null;

// 详情框
const drawer = ref(null);
// 流量详情框
const trafficDrawer = ref(null);
// 工具执行
const toolExecuteDrawer = ref(null);

const nodeInfo = ref({});
// 虚拟机控制台
const dialogList = ref([]);
const virtualComponent = ref(null);
const nodeDetail = ref(null);
// 生成封面

// 生成封面
const isSceneCreate = ref(false);

// 物理设备连接弹窗相关
const materialInterfaceID = ref(null);
const materialInterfaceConVirIP = ref(null);
const materialInterfaceConVirWg = ref(null);
const materialInterfaceData = ref({});
const materialInterfaceList = ref([]);
const materialEqInterfaceList = ref([]);
const choiceMaterialType = ref(0);

// 左侧设备区域相关数据
const componentClassifyType = ref();
const tabMemoryArr = [];

// 保存左边栏拓扑节点实例
let stencil = null;
let stencilNet = null;
let stencilBuildIn = null;
let stencilTool = null;

// 侧边栏拖拽实例
const leftMain = ref(null);
const leftBuildMain = ref(null);
const leftToolMain = ref(null);
const netMain = ref(null);

// 组件列表
const compList = ref([]);
const toolListData = ref([]);

// 扩展 state 对象
Object.assign(state, {
  // 保存 除内置和我的图形 节点信息
  image: [],
  //  设置节点分组类型
  groups: [],
});

// 模拟漏洞列表
// const mockVulnList = [
//   {
//     id: 'CVE-2023-001',
//     title: 'CVE-2023-001',
//     protocol: 'RJ45',
//     desc: '攻击工具利用',
//     attack: 'T1588.001',
//     rate: 0
//   }
// ];

// 漏洞树数据结构
const vulnTreeData = {
  'Kali主机': {
    children: {
      '网络扫描': {
        children: {
          '端口扫描': {
            children: {
              'TCP扫描': {
                children: {
                  'SYN扫描': { children: {} },
                  'ACK扫描': { children: {} },
                  'FIN扫描': { children: {} }
                }
              },
              'UDP扫描': {
                children: {
                  'UDP端口探测': { children: {} },
                  'UDP服务识别': { children: {} }
                }
              }
            }
          },
          '服务识别': {
            children: {
              '版本检测': {
                children: {
                  '服务版本枚举': { children: {} },
                  '漏洞版本匹配': { children: {} }
                }
              }
            }
          }
        }
      },
      '漏洞利用': {
        children: {
          '认证绕过': {
            children: {
              '弱密码攻击': {
                children: {
                  '暴力破解': { children: {} },
                  '字典攻击': { children: {} }
                }
              },
              '会话劫持': {
                children: {
                  'Cookie劫持': { children: {} },
                  'Token劫持': { children: {} }
                }
              }
            }
          }
        }
      }
    }
  },
  '智能融合终端': {
    children: {
      '系统漏洞': {
        children: {
          '缓冲区溢出': {
            children: {
              '栈溢出': {
                children: {
                  'ROP链构造': { children: {} },
                  'Shellcode注入': { children: {} }
                }
              },
              '堆溢出': {
                children: {
                  '堆喷射': { children: {} },
                  'Use After Free': { children: {} }
                }
              }
            }
          },
          '权限提升': {
            children: {
              '本地提权': {
                children: {
                  '内核漏洞利用': { children: {} },
                  'SUID提权': { children: {} }
                }
              }
            }
          }
        }
      }
    }
  },
  '智能断路器': {
    children: {
      '协议漏洞': {
        children: {
          'Modbus协议': {
            children: {
              '功能码攻击': {
                children: {
                  '写寄存器攻击': { children: {} },
                  '读寄存器攻击': { children: {} }
                }
              }
            }
          },
          'CAN总线': {
            children: {
              '消息注入': {
                children: {
                  '恶意指令注入': { children: {} },
                  '总线劫持': { children: {} }
                }
              }
            }
          }
        }
      }
    }
  },
  '配电变压器': {
    children: {
      '控制漏洞': {
        children: {
          '远程控制': {
            children: {
              'SCADA攻击': {
                children: {
                  'HMI劫持': { children: {} },
                  'PLC控制': { children: {} }
                }
              }
            }
          },
          '物理攻击': {
            children: {
              '硬件后门': {
                children: {
                  '固件植入': { children: {} },
                  '硬件劫持': { children: {} }
                }
              }
            }
          }
        }
      }
    }
  }
};



// 当前选中的漏洞路径
const selectedVulnPath = ref([]);



// 模拟攻击路径
const mockPath = ref({
  success: 92,
  steps: [
    {
      from: 'Kali主机',
      to: '智能融合终端',
      desc: '通过RJ45利用CVE-2023-45127漏洞绕过认证',
      attack: 'T1556.002',
      rate: 85
    },
    {
      from: '智能融合终端',
      to: '智能断路器',
      desc: '通过RS485利用CVE-2023-47822执行远程代码',
      attack: 'T1203',
      rate: 75
    },
    {
      from: '智能断路器',
      to: '配电变压器',
      desc: '通过CAN总线发送恶意指令导致断电',
      attack: 'T0859',
      rate: 90
    }
  ]
});

// 模拟战术详情
const mockTactic = ref({
  success: 0,
  path: '',
  target: '',
  scope: '',
  steps: []
});

// 更新攻击路径和战术详情
const updateAttackPathAndTactic = (edge) => {
  try {
    const sourceNode = graph.getCellById(edge.getSourceCellId());
    const targetNode = graph.getCellById(edge.getTargetCellId());
    
    if (sourceNode && targetNode) {
      const sourceName = sourceNode.getAttrByPath('label/text') || '未知节点';
      const targetName = targetNode.getAttrByPath('label/text') || '未知节点';
      
      // 生成新的攻击步骤
      const newStep = {
        from: sourceName,
        to: targetName,
        desc: `通过${getConnectionType(sourceNode, targetNode)}连接${sourceName}到${targetName}`,
        attack: generateAttackTechnique(sourceNode, targetNode),
        rate: Math.floor(Math.random() * 30) + 70 // 70-100%的成功率
      };
      
      // 更新攻击路径
      mockPath.value.steps.push(newStep);
      
      // 重新计算成功率
      const totalRate = mockPath.value.steps.reduce((sum, step) => sum + step.rate, 0);
      mockPath.value.success = Math.round(totalRate / mockPath.value.steps.length);
      
      // 更新战术详情
      updateTacticDetails();
      
      console.log('攻击路径和战术详情已更新:', newStep);
    }
  } catch (error) {
    console.error('更新攻击路径失败:', error);
  }
};

// 从攻击路径中删除连线对应的步骤
const removeEdgeFromAttackPath = (edge) => {
  try {
    const sourceNode = graph.getCellById(edge.getSourceCellId());
    const targetNode = graph.getCellById(edge.getTargetCellId());
    
    if (sourceNode && targetNode) {
      const sourceName = sourceNode.getAttrByPath('label/text') || '未知节点';
      const targetName = targetNode.getAttrByPath('label/text') || '未知节点';
      
      // 查找并删除对应的攻击步骤
      const stepIndex = mockPath.value.steps.findIndex(step => 
        step.from === sourceName && step.to === targetName
      );
      
      if (stepIndex !== -1) {
        // 删除找到的步骤
        const removedStep = mockPath.value.steps.splice(stepIndex, 1)[0];
        console.log('从攻击路径中删除步骤:', removedStep);
        
        // 重新计算成功率
        if (mockPath.value.steps.length > 0) {
          const totalRate = mockPath.value.steps.reduce((sum, step) => sum + step.rate, 0);
          mockPath.value.success = Math.round(totalRate / mockPath.value.steps.length);
        } else {
          // 如果没有步骤了，重置成功率
          mockPath.value.success = 0;
        }
        
        // 更新战术详情
        updateTacticDetails();
        
        console.log('攻击路径和战术详情已更新，删除了步骤:', removedStep);
      } else {
        console.log('未找到对应的攻击步骤:', sourceName, '→', targetName);
      }
    }
  } catch (error) {
    console.error('删除攻击路径步骤失败:', error);
  }
};

// 获取连接类型
const getConnectionType = (sourceNode, targetNode) => {
  const sourceCategory = sourceNode.getData()?.options?.category;
  const targetCategory = targetNode.getData()?.options?.category;
  
  // 根据节点类型判断连接类型
  if (sourceCategory === 8 || targetCategory === 8) {
    return '网络';
  } else if (sourceCategory === 3 || targetCategory === 3) {
    return '物理';
  } else {
    return '虚拟';
  }
};

// 生成攻击技术
const generateAttackTechnique = (sourceNode, targetNode) => {
  const techniques = [
    'T1556.002', // 绕过认证机制
    'T1203',     // 利用客户端执行
    'T0859',     // 系统服务
    'T1562.001', // 禁用或修改工具
    'T1071.001', // 应用层协议
    'T1021.001', // 远程桌面协议
    'T1090.001', // 内部代理
    'T1078.001'  // 默认账户
  ];
  
  return techniques[Math.floor(Math.random() * techniques.length)];
};

// 更新战术详情
const updateTacticDetails = () => {
  const steps = mockPath.value.steps;
  if (steps.length > 0) {
    // 检查是否有详细攻击路径
    const detailedSteps = steps.filter(step => step.isDetailedPath);
    
    if (detailedSteps.length > 0) {
      // 有详细攻击路径时，使用详细路径描述
      const detailedStep = detailedSteps[detailedSteps.length - 1]; // 使用最新的详细路径
      mockTactic.value.path = detailedStep.pathDescription || `${detailedStep.from} > ${detailedStep.intermediateSteps.join(' > ')} > ${detailedStep.to}`;
    } else {
      // 没有详细攻击路径时，使用简单路径
      mockTactic.value.path = steps.map(step => `${step.from} → ${step.to}`).join(' → ');
    }
    
    // 更新攻击目标（最后一个目标节点）
    const lastStep = steps[steps.length - 1];
    mockTactic.value.target = `${lastStep.to}控制权`;
    
    // 更新影响范围
    mockTactic.value.scope = generateImpactScope(steps);
    
    // 更新战术步骤
    mockTactic.value.steps = steps.map((step, index) => ({
      title: getTacticStepTitle(index, step),
      desc: step.desc,
      attack: step.attack
    }));
    
    // 更新整体成功率
    mockTactic.value.success = mockPath.value.success;
  } else {
    // 没有攻击路径时，清空战术详情
    mockTactic.value.path = '';
    mockTactic.value.target = '';
    mockTactic.value.scope = '';
    mockTactic.value.steps = [];
    mockTactic.value.success = 0;
  }
};

// 生成影响范围
const generateImpactScope = (steps) => {
  const scopes = [
    '区域电力中断',
    '关键基础设施破坏',
    '数据泄露',
    '系统瘫痪',
    '网络隔离',
    '服务中断'
  ];
  
  return scopes[Math.floor(Math.random() * scopes.length)];
};

// 获取战术步骤标题
const getTacticStepTitle = (index, step) => {
  const titles = [
    '初始访问',
    '执行',
    '持久化',
    '权限提升',
    '防御规避',
    '凭证访问',
    '发现',
    '横向移动',
    '收集',
    '命令与控制',
    '渗出',
    '影响'
  ];
  
  return titles[index] || `步骤${index + 1}`;
};

const pasteFunc = () => {
  const clipBoard = graph.isClipboardEmpty();
  if (!clipBoard) {
    graph.paste();
  } else {
    ElMessage.warning(`无内容!`);
  }
  hideDetail();
};

const hideDetail = () => {
  // 隐藏详情面板
};

// 关闭节点信息面板
const closeNodeInfoPanel = () => {
  selectedNodeInfo.value = null;
};

// 处理节点点击事件
const handleNodeClick = (node) => {
  const nodeData = node.toJSON();
  selectedNodeInfo.value = {
    id: nodeData.id,
    name: nodeData.attrs?.label?.text || nodeData.attrs?.text?.text || '未命名',
    shape: nodeData.shape,
    position: nodeData.position,
    size: nodeData.size,
    attrs: nodeData.attrs,
    options: nodeData.options || nodeData.attrs?.image?.options,
  };
};

// 获取分类名称
const getCategoryName = (category) => {
  const categoryMap = {
    1: '虚拟机',
    2: '容器',
    3: '物理设备',
    4: '网络设备',
    5: '内置图形',
    6: '交换设备',
    7: '物理设备',
    8: '攻击工具',
    9: '工具资源',
    10: '路由设备'
  };
  return categoryMap[category] || '未知类型';
};



// 监听websocket消息通知
const taskFunc = data => {
  const { msg } = data;
  const info = msg.update;
  allCount.value = msg.count;
  if (info) {
    let mapList = sessionStorage.getItem('mapList');
    mapList = JSON.parse(mapList);
    if (mapList && mapList[ID.value] == info.ID) {
      if (info.status == 3) {
        drawer.value = false;
        refreshGraph(ID.value, graph);
      } else {
        delete mapList[ID.value];
        sessionStorage.setItem('mapList', JSON.stringify(mapList));
      }
    }
    let nodeList = sessionStorage.getItem('nodeList');
    nodeList = JSON.parse(nodeList);
    if (nodeList[info.ID]) {
      let options = nodeList[info.ID].options;
      if (info.status == 3) {
        if (
          options.indexOf('start') !== -1 ||
          options.indexOf('stop') !== -1 ||
          options.indexOf('reset') !== -1
        ) {
          changeNodeStatus(info.ID, nodeList[info.ID].ID, options);
        } else if (options == 'acl') {
          if (nodeDetail.value.getMyNodeList) {
            nodeDetail.value.getMyNodeList();
          }
          delete nodeList[info.ID];
          sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
        }
      } else {
        delete nodeList[info.ID];
        sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
      }
    }
  }
};

// 监听子组件方法刷新画布
const refreshTopo = () => {
  refreshGraph(ID.value, graph);
};

const setTaskId = async (taskID, id, flag) => {
  let mapList = sessionStorage.getItem('nodeList') || '{}';
  mapList = JSON.parse(mapList);
  mapList[taskID] = { ID: id, options: flag };
  sessionStorage.setItem('nodeList', JSON.stringify(mapList));
};

const changeNodeStatus = async (taskID, id, flag) => {
  let cell = graph.getCellById(id);
  cell.attr(
    '.rank/fill',
    flag === 'stop'
      ? '#F56C6C'
      : flag === 'start' || flag === 'restart'
      ? '#67C23A'
      : '#909399',
  );
  let nodeList = sessionStorage.getItem('nodeList');
  nodeList = JSON.parse(nodeList);
  delete nodeList[taskID];
  sessionStorage.setItem('nodeList', JSON.stringify(nodeList));
};

const editNodeDetail = async (arg, image) => {
  let cell = graph.getCellById(arg.id);
  cell.attr('text/text', arg.attrs.text.text);
  cell.attr('image/xlink:href', arg.attrs.image['xlink:href']);
  if (image) {
    cell.attr('.rankCenter/xlink:href', simulationCover(image));
  }
};

const openFunc = async param => {
  let arg = param.cell;
  let flag = param.flag;
  let res = await editScene(ID.value, arg.id, { operation: flag });
  if (!(res instanceof Error)) {
    await setTaskId(res.data.taskID, arg.id, flag);
  }
};

const editNodeFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  drawer.value = true;
};

const trafficReplayFunc = async arg => {
  nodeInfo.value = arg.toJSON();
  nodeInfo.value['id'] = arg.id;
  trafficDrawer.value = true;
};

const closeTrafficFunc = () => {
  trafficDrawer.value = false;
};

const toolExecuteFunc = async arg => {
  emitter.emit('debugTool', { id: ID.value, instanceID: arg.id });
};

const closeToolExecuteFunc = () => {
  trafficDrawer.value = false;
};

const closeFunc = () => {
  drawer.value = false;
};

const changeVirtual = async arg => {
  if (arg.category == 1) {
    let index = dialogList.value.findIndex(item => {
      return item.RID == arg.id;
    });
    getVirtual(ID.value, arg.id, { read: false, record: false }).then(
      async res => {
        if (!(res instanceof Error)) {
          const {
            data: { token, uri },
          } = res;

          let virtualData = {
            token: token,
            uri: uri,
            id: ID.value,
            RID: arg.id,
            name: arg.name,
            read: false,
            record: false,
            zIndex: 10000,
          };

          if (index == -1) {
            dialogList.value.push(virtualData);
            virtualComponent.value?.openFunc(virtualData);
          } else {
            topDialog(virtualData);
          }
        }
      },
    );
  } else if (arg.category == 2) {
    let index = dockerDialogList.value.findIndex(item => {
      return item.RID == arg.id;
    });
    let dockerData = {
      id: ID.value,
      RID: arg.id,
      name: arg.name,
      zIndex: 10000,
    };

    if (index == -1) {
      dockerDialogList.value.push(dockerData);
      dockerComponent.value?.openFunc(dockerData, false);
    } else {
      topDockerDialog(dockerData);
    }
  }
};

const containerLogs = async arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.id;
  });
  let nodeInfo = await getDescFunc({ id: ID.value, uuid: arg.id });
  if (nodeInfo.data.dockerID == 0) {
    ElMessage.warning('获取容器id失败');
    return;
  }
  let dockerData = {
    id: ID.value,
    RID: arg.id,
    name: nodeInfo.data.name,
    zIndex: 10000,
    data: {
      ID: nodeInfo.data.dockerID,
    },
  };

  if (index == -1) {
    dockerLogList.value.push(dockerData);
  } else {
    topDockerLogDialog(dockerData);
  }
};

const topDialog = item => {
  virtualComponent.value?.focusFunc(item);
};

const topDockerDialog = item => {
  dockerComponent.value?.focusFunc(item);
};

const topDockerLogDialog = item => {
  dockerLogComponent.value?.focusFunc(item);
};

const closeVirtualFunc = arg => {
  let index = dialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dialogList.value.splice(index, 1);
  }
};

const closeDockerFunc = arg => {
  let index = dockerDialogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerDialogList.value.splice(index, 1);
  }
};

const closeDockerLogFunc = arg => {
  let index = dockerLogList.value.findIndex(item => {
    return item.RID == arg.RID;
  });
  if (index !== -1) {
    dockerLogList.value.splice(index, 1);
  }
};

// 自定义拖拽
const currentDragItem = ref(null);

function onDragStart(item, event) {
  // 设置拖拽数据，但不阻止默认行为
  currentDragItem.value = item;
}

function onDrop(e) {
  e.preventDefault();
  console.log('拖拽释放:', e);
  console.log('当前拖拽项:', currentDragItem.value);
  
  if (!currentDragItem.value) {
    console.log('没有拖拽项，返回');
    return;
  }
  
  if (!graph) {
    console.log('画布未初始化，返回');
    return;
  }
  
  // 获取画布坐标
  const { x, y } = graph.clientToLocal(e.clientX, e.clientY);
  console.log('画布坐标:', { x, y });
  
  // 用initNode生成节点数据
  let nodeArr = [];

  if (!currentDragItem.value?.options?.attrs) {
    nodeArr = initNode([{ name: '自定义', fileList: [currentDragItem.value] }]);
  } else {
    nodeArr = [{ groupName: '自定义', list: [currentDragItem.value.options] }];
  }

  console.log('生成的节点数组:', nodeArr);

  if (nodeArr.length && nodeArr[0].list.length) {
    const nodeData = nodeArr[0].list[0];
    
    // 检查画布上是否已存在相同名称的节点
    const allNodes = graph.getNodes();
    const newNodeName = nodeData.attrs?.text?.text || currentDragItem.value.name;
    
    console.log('新节点名称:', newNodeName);
    console.log('画布上现有节点数量:', allNodes.length);
    
    // 检查是否有重复的节点名称
    const isDuplicate = allNodes.some(existingNode => {
      // 获取现有节点的名称，支持多种属性路径
      let existingNodeName = null;
      
      // 尝试从不同的属性路径获取节点名称
      if (existingNode.attr('text/text')) {
        existingNodeName = existingNode.attr('text/text');
      } else if (existingNode.attr('label/text')) {
        existingNodeName = existingNode.attr('label/text');
      } else if (existingNode.attr('body/text')) {
        existingNodeName = existingNode.attr('body/text');
      } else if (existingNode.getData()?.name) {
        existingNodeName = existingNode.getData().name;
      }
      
      console.log('现有节点名称:', existingNodeName);
      
      // 如果找到了名称，进行精确匹配
      if (existingNodeName && existingNodeName === newNodeName) {
        return true;
      }
      
      return false;
    });
    
    if (isDuplicate) {
      console.log('检测到重复节点，不允许添加');
      ElMessage.warning(`画布上已存在名称为"${newNodeName}"的节点，不能重复添加`);
      currentDragItem.value = null;
      return;
    }
    
    nodeData.x = x;
    nodeData.y = y;
    console.log('添加节点:', nodeData);
    graph.addNode(nodeData);
    ElMessage.success(`成功添加节点"${newNodeName}"`);
  } else {
    console.log('节点数组为空或没有列表项');
  }
  currentDragItem.value = null;
}

// 初始化画布
const initContainer = async () => {
  if (!graphMain.value) return;

  graph = initGraph(graphMain.value);
  
  // 检查是否为测试数据
  if (ID.value === 'test-scene-001') {
    // 使用测试数据初始化
    await initTestData();
  } else {
    // 初始化节点/连线
    sceneDetail = await refreshGraph(ID.value, graph);
    clusterID.value = sceneDetail.value.clusterID;
    isCopy.value = sceneDetail.value.isCopy;
  }

  // 获取初始化边信息
  graphStore.edges = JSON.stringify(graph.getEdges());
  
  // 添加节点点击事件监听器
  graph.on('node:click', ({ node }) => {
    handleNodeClick(node);
  });
};

// 初始化测试数据
const initTestData = async () => {
  try {
    // 设置测试场景详情
    sceneDetail.value = testSceneData;
    clusterID.value = testSceneData.clusterID;
    isCopy.value = testSceneData.isCopy;
    
    // 清空画布
    graph.clearCells();
    
    // 添加测试节点
    testSceneData.nodes.forEach(nodeData => {
      const node = graph.addNode(nodeData);
      
      // 添加节点hover事件，显示/隐藏连接点
      node.on('mouseenter', () => {
        // 显示所有连接点
        node.getPorts().forEach(port => {
          node.setPortProp(port.id, 'attrs/circle/style/visibility', 'visible');
        });
      });
      
      node.on('mouseleave', () => {
        // 隐藏所有连接点
        node.getPorts().forEach(port => {
          node.setPortProp(port.id, 'attrs/circle/style/visibility', 'hidden');
        });
      });
    });
    
    // 添加测试连线
    testSceneData.edges.forEach(edgeData => {
      graph.addEdge(edgeData);
    });
    
    // 监听连线创建事件
    graph.on('edge:connected', ({ edge, isNew }) => {
      console.log('连线事件触发:', { edge: edge.id, isNew });
      if (isNew) {
        console.log('新连线创建，显示弹框');
        // 检查是否已存在相同的连线
        const sourceId = edge.getSourceCellId();
        const targetId = edge.getTargetCellId();
        const existingEdges = graph.getEdges().filter(e => 
          e.id !== edge.id && 
          e.getSourceCellId() === sourceId && 
          e.getTargetCellId() === targetId
        );
        
        if (existingEdges.length > 0) {
          // 存在重复连线，删除新创建的连线，显示警告
          graph.removeEdge(edge);
          ElMessage.warning('两个节点之间已存在连线，沿用之前的连线');
          return;
        }
        
        // 更新攻击路径和战术详情
        updateAttackPathAndTactic(edge);
        
        showConnectionInfo(edge, '新建连接');
      }
    });
    
    // 监听连线重新连接完成事件（只在连接操作真正完成时触发）
    graph.on('edge:reconnected', ({ edge }) => {
      console.log('连线重新连接完成:', edge.id);
      
      // 更新攻击路径和战术详情
      updateAttackPathAndTactic(edge);
      
      showConnectionInfo(edge, '重新连接');
    });
    
    // 监听连线删除事件
    graph.on('edge:removed', ({ edge }) => {
      console.log('连线被删除:', edge.id);
      
      // 从攻击路径中删除对应的步骤
      removeEdgeFromAttackPath(edge);
    });
    
    console.log('测试数据初始化成功');
  } catch (error) {
    console.error('测试数据初始化失败:', error);
    ElMessage.error('测试数据初始化失败');
  }
};

// 显示连线信息弹框
const showConnectionInfo = (edge, operationType = '连接') => {
  console.log('showConnectionInfo 被调用:', edge, operationType);
  try {
    const sourceNode = graph.getCellById(edge.getSourceCellId());
    const targetNode = graph.getCellById(edge.getTargetCellId());
    
    console.log('源节点:', sourceNode);
    console.log('目标节点:', targetNode);
    
    if (sourceNode && targetNode) {
      // 获取节点颜色
      const getNodeColor = (node) => {
        const attrs = node.getAttrs();
        return attrs.body?.fill || '#4285f4';
      };
      
      // 获取节点类型名称
      const getNodeTypeName = (node) => {
        const options = node.getData()?.options;
        if (options?.category) {
          const categoryMap = {
            1: '虚拟机',
            2: '容器',
            3: '物理设备',
            4: '网络设备',
            5: '内置图形',
            6: '交换设备',
            7: '物理设备',
            8: '攻击工具',
            9: '工具资源',
            10: '路由设备'
          };
          return categoryMap[options.category] || '未知类型';
        }
        return '未知类型';
      };
      
      connectionInfo.value = {
        source: {
          id: sourceNode.id,
          name: sourceNode.getAttrByPath('label/text') || '未命名',
          type: getNodeTypeName(sourceNode),
          color: getNodeColor(sourceNode)
        },
        target: {
          id: targetNode.id,
          name: targetNode.getAttrByPath('label/text') || '未命名',
          type: getNodeTypeName(targetNode),
          color: getNodeColor(targetNode)
        },
        edgeId: edge.id,
        createTime: new Date().toLocaleString('zh-CN'),
        operationType: operationType
      };
      
      connectionInfoShow.value = true;
    }
  } catch (error) {
    console.error('显示连线信息失败:', error);
    ElMessage.error('显示连线信息失败');
  }
};

// 关闭连线信息弹框
const closeConnectionInfo = () => {
  connectionInfoShow.value = false;
  connectionInfo.value = {
    source: null,
    target: null,
    edgeId: '',
    createTime: '',
    operationType: ''
  };
};

// 查看连线详情
const viewConnectionDetails = () => {
  // 这里可以添加查看连线详情的逻辑
  console.log('查看连线详情:', connectionInfo.value);
  ElMessage.info('连线详情功能开发中...');
  closeConnectionInfo();
};

// 物理设备连接相关函数
const changeMaterialInterface = async item => {
  if (choiceMaterialType.value == 1) {
    materialInterfaceConVirIP.value = '';
    // 计算链路设备连接类型IP
    for (let i = 0; i < materialInterfaceList.value.length; i++) {
      if (item === materialInterfaceList.value[i].ID) {
        const _ip = getRandomIP(materialInterfaceList.value[i].host);
        materialInterfaceConVirIP.value = _ip.randomIp + '/' + _ip.mask;
      }
    }
    return;
  }
};

const getMaterialInterface = async () => {
  // 获取物理设备接口信息的实现
  // 这里可以根据实际需求实现
  console.log('获取物理设备接口信息');
};

const matchMaterialInterface = async () => {
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
};

const unmatchMaterialInterface = () => {
  materialInterfaceShow.value = false;
  materialInterfaceID.value = null;
  materialInterfaceData.value = {};
  materialInterfaceList.value = [];
  materialEqInterfaceList.value = [];
  materialInterfaceConVirWg.value = null;
  materialInterfaceConVirIP.value = null;
  choiceMaterialType.value = ref(0);
};

// 生成当前拓扑信息
const saveScene = async transfer => {
  isSceneCreate.value = true;
  graph.toPNG(
    async dataUri => {
      const file = dataURLtoFile(dataUri, 'topology');
      const formData = new FormData();
      formData.append('file', file);
      const resUpload = await uploadMyGraphicsFile(formData);
      if (!(resUpload instanceof Error)) {
        await editSceneList({
          id: ID.value,
          name: NAME,
          cover: resUpload.data?.file_path,
        });
        if (!transfer) {
          ElMessage.success(`生成成功!`);
        }
        if (transfer === true) {
          let resDetail = await transferScene(ID.value);
          if (!(resDetail instanceof Error)) {
            ElMessage.success(`操作成功!`);
          }
        }
        isSceneCreate.value = false;
      } else {
        isSceneCreate.value = false;
      }
    },
    { quality: 1, padding: 10, width: 240, height: 240 },
  );
};

// ================== 左侧设备区域相关方法 ==================

// 获取图片路径
const getImage = arg => {
  if (arg == 'circle') {
    return new URL('./image/circle.png', import.meta.url).href;
  } else if (arg == 'rect') {
    return new URL('./image/rect.png', import.meta.url).href;
  } else if (arg == 'polygon') {
    return new URL('./image/polygon.png', import.meta.url).href;
  } else if (arg == 'ellipse') {
    return new URL('./image/ellipse.png', import.meta.url).href;
  } else if (arg == 'line') {
    return new URL('./image/line.svg', import.meta.url).href;
  } else {
    return new URL('./image/square.png', import.meta.url).href;
  }
};

// 获取所有分组名称
function getAllGroupNames(list) {
  return list.map(group => group.name);
}

// el-collapse折叠/展开事件
function handleCollapseChange(names) {
  tabActiveNames[activeTab.value] = names;
  activeNames.value = names;
}

// 资源数据源，直接用state.image
// 查询和分类过滤后的资源列表
const filteredResourceList = computed(() => {
  let list = state.image || [];
  if (searchWord.value) {
    // 只保留fileList中有匹配项的分组
    list = list
      .map(group => ({
        ...group,
        fileList: group.fileList.filter(item =>
          item.name.includes(searchWord.value),
        ),
      }))
      .filter(group => group.fileList.length > 0);
  }
  return list;
});

// 监听 filteredResourceList 变化，更新 tabActiveNames 和 activeNames
watch(
  () => filteredResourceList.value,
  list => {
    tabActiveNames[activeTab.value] = getAllGroupNames(list);
    activeNames.value = tabActiveNames[activeTab.value];
  },
  { immediate: true },
);

// 内置图形tab专用资源列表
const builtInResourceList = computed(() => {
  let list = sceneBuiltBaseNode.map((item, idx) => ({
    name: '',
    path: '', // 可自定义icon路径
    options: {
      ...item,
      category: 5,
    },
  }));
  // 支持搜索
  if (searchWord.value) {
    list = list.filter(item => item.name.includes(searchWord.value));
  }

  return [
    {
      name: '内置图形',
      fileList: list,
    },
  ];
});

// 监听 builtInResourceList 变化，更新 tabActiveNames 和 activeNames
watch(
  () => builtInResourceList.value,
  list => {
    tabActiveNames[activeTab.value] = getAllGroupNames(list);
    activeNames.value = tabActiveNames[activeTab.value];
  },
  { immediate: true },
);



// 刷新左侧面板
const refreshScene = () => {
  showLeft.value = !showLeft.value;
};

// Tab切换处理
const handleTabClick = async val => {
  let name = val.props.name;

  tabMemoryArr.push(name);
  if (name == 'component') {
    await initComponentsMenu();
  }
  if (name == 'network') {
    await initNetMenu(clusterID);
  }
  if (name == 'builtIn') {
    await openBuildIn();

    tabActiveNames[activeTab.value] = getAllGroupNames(
      builtInResourceList.value,
    );
    activeNames.value = tabActiveNames[activeTab.value];
  }
  if (name == 'tool') {
    await initTool();
  }
};

// 初始化组件菜单
const initComponentsMenu = async () => {
  try {
    // 使用默认数据，避免API调用失败
    const res = defaultSceneTopo;
    if (!(res instanceof Error)) {
      compList.value = [].concat(res?.data || []);
      let imageArr = [];
      for (let i = 0; i < compList.value.length; i++) {
        let item = compList.value[i];
        let fileList = [];
        for (let j = 0; j < item.data.length; j++) {
          let itemChild = item.data[j];
          itemChild['componentCategory'] = itemChild.category;
          itemChild['category'] = categoryMapList[itemChild.kindName];
          fileList.push({
            name: itemChild.name,
            path: simulationCover(itemChild.icon.path),
            options: itemChild,
          });
        }
        imageArr.push({
          name: item.name,
          fileList: fileList || [],
        });
      }
      state.image = imageArr;
      state.groups = [].concat(f(compList.value));
      // 初始化stencil
      if (stencil) {
        leftMain.value?.removeChild(stencil.container);
      }
      stencil = initStencil(graph, state.groups, 4);
      registerNode(state.image, stencil);
      leftMain.value?.appendChild(stencil.container);

      function f(arr) {
        return (
          arr?.map(el => ({
            name: el.name,
            title: el.name,
            collapsable: true,
          })) || []
        );
      }
    }
  } catch (error) {
    console.error('初始化组件菜单失败:', error);
  }
};

// 初始化网络菜单
const initNetMenu = async (clusterID) => {
  try {
    const res = await getVulnEnvironmentList({ page: 1, pageSize: 9999 });
    if (!(res instanceof Error)) {
      state.image = res.data?.list || [];
      // 初始化stencil
      if (stencilNet) {
        netMain.value?.removeChild(stencilNet.container);
      }
      stencilNet = initStencil(graph, state.image, 4);
      registerNode(state.image, stencilNet);
      netMain.value?.appendChild(stencilNet.container);
    }
  } catch (error) {
    console.error('初始化网络菜单失败:', error);
  }
};

// 初始化工具
const initTool = async () => {
  state.groups = [
    {
      name: 'tool',
      title: '工具资源',
      collapsable: true,
      fileList: [],
    },
  ];
  // 查询列表数据
  await getToolList({ page: 1, pageSize: 9999 }).then(res => {
    toolListData.value = res.data?.list;
  });
  for (let t in toolListData.value) {
    let _img = '/img/tool_vuln_scan.svg';
    switch (toolListData.value[t].type) {
      case 1:
        _img = '/img/tool_vuln_scan.svg';
        break;
      case 2:
        _img = '/img/tool_blast.svg';
        break;
      case 4:
        _img = '/img/tool_poc.svg';
        break;
      case 5:
        _img = '/img/tool_traffic_monitor.svg';
        break;
    }
    // 将数据填充到分类中
    const options = toolListData.value[t];
    options.category = 9;
    options.name = toolListData.value[t].name;
    options.mirrorImage = 1;
    options.componentCategory = toolListData.value[t].type.toString();
    options.kindName = '工具资源';
    state.groups[0].fileList.push({
      name: toolListData.value[t].name,
      path: simulationCover(_img),
      options: options,
    });
  }
  stencilTool = initStencil(graph, state.groups, 4);
  registerNode(state.groups, stencilTool);
  leftToolMain.value?.appendChild(stencilTool.container);
};

// 打开内置图形
const openBuildIn = async () => {
  state.groups = [
    {
      name: 'builtIn',
      title: '内置图形',
      collapsable: true,
    },
  ];
  stencilBuildIn = initStencil(graph, state.groups, 4);
  stencilBuildIn.load(sceneBuiltBaseNode, 'builtIn');
  registerNode(state.groups, stencilBuildIn);
  leftBuildMain.value?.appendChild(stencilBuildIn.container);
};

// 筛选组件
const changeRadio = async (val, index) => {
  designIndexLast.value = designIndex.value;
  componentClassifyType.value = val;
  designIndex.value = index;

  if (designIndexLast.value === designIndex.value) {
    designIndex.value = -1;
    componentClassifyType.value = '';
  }

  leftMain.value?.removeChild(stencil.container);
  if (val != 8) {
    await initComponentsMenu();
  }
};

// 获取组件类型列表
const getComponentTypeList = async () => {
  try {
    const res = await getComponentType();
    radioGroup.value = res.data;
  } catch (err) {
    console.log('err组件类型默认数据');
    radioGroup.value = [
      { key: 1, nickname: '虚拟机' },
      { key: 2, nickname: '容器' },
      { key: 3, nickname: '物理设备' },
      { key: 4, nickname: '网络设备' },
      { key: 8, nickname: '攻击工具' }
    ];
  }
};

// 注册节点
const registerNode = (groups, stencil) => {
  if (groups && groups.length > 0) {
    groups.forEach(group => {
      if (group.fileList && group.fileList.length > 0) {
        group.fileList.forEach(item => {
          if (item.options) {
            stencil.load([item.options], group.name);
          }
        });
      }
    });
  }
};

onMounted(async () => {
  // 初始化 DATA_INFO
  if (typeof window !== 'undefined' && !window.DATA_INFO) {
    window.DATA_INFO = {
      currentRoute: {
        name: router.currentRoute.value.name || ''
      }
    };
  }
  
  await initContainer();
  
  // 初始化左侧设备区域
  await getComponentTypeList();
  // 设置默认组件类型
  if (radioGroup.value.length > 0) {
    componentClassifyType.value = radioGroup.value[0]?.key;
    designIndex.value = 0;
  }
  await initComponentsMenu();
  
  // 监听增加节点/连线
  eventBus.on('addScreenNode', () => {});
  // 监听移除节点/连线
  eventBus.on('removeScreenNode', () => {});
  // 监听修改节点/连线
  eventBus.on('changePutGraph', () => {});
  // 展开详情编辑框
  eventBus.on('editNode', editNodeFunc);
  // 流量重放
  eventBus.on('trafficReplay', trafficReplayFunc);
  // 工具执行
  eventBus.on('toolExecute', toolExecuteFunc);
  // 控制台
  eventBus.on('changeVirtual', changeVirtual);
  // 容器日志
  eventBus.on('containerLogs', containerLogs);
  // 编辑开启状态
  eventBus.on('openFunc', openFunc);
  // 关闭详情
  eventBus.on('hideDetail', closeFunc);
  // 监听websocket消息通知
  emitter.on('Task', taskFunc);

  sessionStorage.setItem('nodeList', '{}');

  // 监听主题管理改变
  emitter.on('changeTheme', initContainer);
});

onUnmounted(async () => {
  eventBus.off('addScreenNode');
  eventBus.off('editNode', editNodeFunc);
  eventBus.off('removeScreenNode');
  eventBus.off('changePutGraph');
  eventBus.off('changeVirtual', changeVirtual);
  eventBus.off('containerLogs', containerLogs);
  eventBus.off('openFunc', openFunc);
  eventBus.off('trafficReplay', trafficReplayFunc);
  eventBus.off('toolExecute', toolExecuteFunc);
  eventBus.off('hideDetail', closeFunc);
  emitter.off('Task', taskFunc);
  emitter.off('changeTheme', initContainer);
  showContext(false);
});

// 过滤后的攻击路径步骤
const filteredPathSteps = computed(() => {
  if (!selectedNodeInfo.value) {
    // 如果没有选中节点，显示所有步骤
    return mockPath.value.steps;
  }
  
  const nodeName = selectedNodeInfo.value.name;
  // 只显示与选中节点相关的步骤
  return mockPath.value.steps.filter(step => 
    step.from === nodeName || step.to === nodeName
  );
});

// 清除节点筛选
const clearNodeFilter = () => {
  selectedNodeInfo.value = null;
};

// 判断是否为目标节点（有入边）
const isTargetNode = computed(() => {
  if (!selectedNodeInfo.value || !graph) return false;
  
  const nodeId = selectedNodeInfo.value.id;
  
  // 排除攻击路径节点（ID 以 intermediate- 开头的节点）
  if (nodeId && nodeId.startsWith('intermediate-')) {
    return false;
  }
  
  const edges = graph.getEdges();
  
  // 检查是否有以该节点为目标的边
  return edges.some(edge => edge.getTargetCellId() === nodeId);
});

// 获取当前节点的漏洞树数据
const currentNodeVulnTree = computed(() => {
  if (!selectedNodeInfo.value) return null;
  
  const nodeName = selectedNodeInfo.value.name;
  return vulnTreeData[nodeName] || vulnTreeData['Kali主机'] || null;
});

// 获取当前层级的漏洞选项
const currentVulnOptions = computed(() => {
  if (!currentNodeVulnTree.value) return {};
  
  let currentLevel = currentNodeVulnTree.value;
  
  // 根据已选择的路径导航到当前层级
  for (let i = 0; i < selectedVulnPath.value.length; i++) {
    const pathItem = selectedVulnPath.value[i];
    if (currentLevel.children && currentLevel.children[pathItem]) {
      currentLevel = currentLevel.children[pathItem];
    } else {
      break;
    }
  }
  
  return currentLevel.children || {};
});

// 检查是否有子项
const hasChildren = (vuln) => {
  return vuln.children && Object.keys(vuln.children).length > 0;
};

// 选择漏洞选项
const selectVulnOption = (optionName) => {
  selectedVulnPath.value.push(optionName);
  
  // 检查是否到达叶子节点
  const currentLevel = getCurrentVulnLevel();
  if (!hasChildren(currentLevel)) {
    // 到达叶子节点，可以生成攻击路径
    console.log('到达漏洞树叶子节点:', selectedVulnPath.value);
  }
};

// 移除漏洞路径项
const removeVulnPathItem = (index) => {
  selectedVulnPath.value.splice(index);
};

// 获取当前漏洞层级
const getCurrentVulnLevel = () => {
  if (!currentNodeVulnTree.value) return null;
  
  let currentLevel = currentNodeVulnTree.value;
  
  for (const pathItem of selectedVulnPath.value) {
    if (currentLevel.children && currentLevel.children[pathItem]) {
      currentLevel = currentLevel.children[pathItem];
    } else {
      break;
    }
  }
  
  return currentLevel;
};

// 生成攻击路径
const generateAttackPath = () => {
  if (selectedVulnPath.value.length === 0) return;
  
  try {
    // 找到以当前节点为目标的边
    const targetNodeId = selectedNodeInfo.value.id;
    const edges = graph.getEdges();
    const targetEdge = edges.find(edge => edge.getTargetCellId() === targetNodeId);
    
    if (!targetEdge) {
      ElMessage.warning('未找到以该节点为目标的连线');
      return;
    }
    
    const sourceNodeId = targetEdge.getSourceCellId();
    const sourceNode = graph.getCellById(sourceNodeId);
    const targetNode = graph.getCellById(targetNodeId);
    
    if (!sourceNode || !targetNode) {
      ElMessage.error('节点信息获取失败');
      return;
    }
    
    // 生成中间节点
    const intermediateNodes = [];
    
    // 计算节点位置 - 优化布局避免遮挡
    const sourcePos = sourceNode.getPosition();
    const targetPos = targetNode.getPosition();
    const sourceSize = sourceNode.getSize();
    const targetSize = targetNode.getSize();
    
    // 计算起点和终点的边界
    const sourceBounds = {
      left: sourcePos.x,
      right: sourcePos.x + sourceSize.width,
      top: sourcePos.y,
      bottom: sourcePos.y + sourceSize.height
    };
    
    const targetBounds = {
      left: targetPos.x,
      right: targetPos.x + targetSize.width,
      top: targetPos.y,
      bottom: targetPos.y + targetSize.height
    };
    
    // 计算路径方向
    const dx = targetPos.x - sourcePos.x;
    const dy = targetPos.y - sourcePos.y;
    const distance = Math.sqrt(dx * dx + dy * dy);
    
    // 确定偏移方向（垂直于连线方向）
    const offsetDirection = Math.atan2(dy, dx) + Math.PI / 2;
    const baseOffsetDistance = 120; // 基础偏移距离
    
    // 生成中间节点位置 - 优化叶子节点布局
    for (let i = 0; i < selectedVulnPath.value.length; i++) {
      const progress = (i + 1) / (selectedVulnPath.value.length + 1);
      
      // 基础位置（在起点和终点之间）
      const baseX = sourcePos.x + dx * progress;
      const baseY = sourcePos.y + dy * progress;
      
      // 计算垂直偏移方向（垂直于连线方向）
      const offsetDirection = Math.atan2(dy, dx) + Math.PI / 2;
      
      // 计算节点在垂直方向上的分布
      const totalNodes = selectedVulnPath.value.length;
      const nodeSpacing = 80; // 节点之间的垂直间距
      const totalHeight = (totalNodes - 1) * nodeSpacing;
      const startOffset = -totalHeight / 2; // 从中心开始分布
      
      // 计算当前节点的垂直偏移
      const verticalOffset = startOffset + i * nodeSpacing;
      const nodeOffsetX = Math.cos(offsetDirection) * 120; // 水平偏移距离
      const nodeOffsetY = Math.sin(offsetDirection) * 120 + verticalOffset;
      
      let x = baseX + nodeOffsetX;
      let y = baseY + nodeOffsetY;
      
      // 检查是否与现有节点重叠，如果重叠则调整位置
      let finalX = x;
      let finalY = y;
      
      // 检查与起点和终点的距离
      const minDistance = 80; // 与起点终点的最小距离
      
      // 与起点的距离检查
      const distToSource = Math.sqrt((x - sourcePos.x) ** 2 + (y - sourcePos.y) ** 2);
      if (distToSource < minDistance) {
        const angle = Math.atan2(y - sourcePos.y, x - sourcePos.x);
        finalX = sourcePos.x + Math.cos(angle) * minDistance;
        finalY = sourcePos.y + Math.sin(angle) * minDistance;
      }
      
      // 与终点的距离检查
      const distToTarget = Math.sqrt((finalX - targetPos.x) ** 2 + (finalY - targetPos.y) ** 2);
      if (distToTarget < minDistance) {
        const angle = Math.atan2(finalY - targetPos.y, finalX - targetPos.x);
        finalX = targetPos.x + Math.cos(angle) * minDistance;
        finalY = targetPos.y + Math.sin(angle) * minDistance;
      }
      
      // 检查与画布中所有现有节点的距离
      const allNodes = graph.getNodes();
      const nodeSize = { width: 80, height: 40 };
      const nodeMargin = 20; // 节点之间的最小间距
      
      for (const existingNode of allNodes) {
        const existingPos = existingNode.getPosition();
        const existingSize = existingNode.getSize();
        
        // 计算两个节点的边界
        const node1Bounds = {
          left: finalX,
          right: finalX + nodeSize.width,
          top: finalY,
          bottom: finalY + nodeSize.height
        };
        
        const node2Bounds = {
          left: existingPos.x,
          right: existingPos.x + existingSize.width,
          top: existingPos.y,
          bottom: existingPos.y + existingSize.height
        };
        
        // 检查是否重叠
        const isOverlapping = !(
          node1Bounds.right < node2Bounds.left ||
          node1Bounds.left > node2Bounds.right ||
          node1Bounds.bottom < node2Bounds.top ||
          node1Bounds.top > node2Bounds.bottom
        );
        
        if (isOverlapping) {
          // 计算避开方向
          const centerX1 = finalX + nodeSize.width / 2;
          const centerY1 = finalY + nodeSize.height / 2;
          const centerX2 = existingPos.x + existingSize.width / 2;
          const centerY2 = existingPos.y + existingSize.height / 2;
          
          const dx = centerX1 - centerX2;
          const dy = centerY1 - centerY2;
          const distance = Math.sqrt(dx * dx + dy * dy);
          
          if (distance > 0) {
            // 计算需要移动的距离
            const minSeparation = (nodeSize.width + existingSize.width) / 2 + nodeMargin;
            const moveDistance = minSeparation - distance;
            
            // 移动节点到安全位置
            finalX += (dx / distance) * moveDistance;
            finalY += (dy / distance) * moveDistance;
          } else {
            // 如果中心点重合，随机选择一个方向
            const randomAngle = Math.random() * 2 * Math.PI;
            const moveDistance = nodeMargin + Math.max(nodeSize.width, nodeSize.height) / 2;
            finalX += Math.cos(randomAngle) * moveDistance;
            finalY += Math.sin(randomAngle) * moveDistance;
          }
        }
      }
      
      // 检查与其他中间节点的距离
      for (const existingNode of intermediateNodes) {
        const existingPos = existingNode.getPosition();
        const distToExisting = Math.sqrt((finalX - existingPos.x) ** 2 + (finalY - existingPos.y) ** 2);
        if (distToExisting < nodeSpacing) { // 中间节点之间的最小距离
          const angle = Math.atan2(finalY - existingPos.y, finalX - existingPos.x);
          finalX = existingPos.x + Math.cos(angle) * nodeSpacing;
          finalY = existingPos.y + Math.sin(angle) * nodeSpacing;
        }
      }
      
      const nodeId = `intermediate-${Date.now()}-${i}`;
      const nodeData = {
        id: nodeId,
        shape: 'rect',
        x: finalX,
        y: finalY,
        width: 80, // 缩小叶子节点宽度
        height: 40, // 缩小叶子节点高度
        attrs: {
          body: {
            fill: '#ff6b6b',
            stroke: '#d14545',
            strokeWidth: 1,
            rx: 4, // 减小圆角
            ry: 4
          },
          label: {
            text: selectedVulnPath.value[i],
            fill: '#ffffff',
            fontSize: 9, // 减小字体
            fontWeight: 'bold',
            textAnchor: 'middle',
            textVerticalAnchor: 'middle'
          }
        },
        options: {
          kindName: selectedVulnPath.value[i],
          category: 5
        }
      };
      
      const node = graph.addNode(nodeData);
      intermediateNodes.push(node);
    }
    
    // 创建新的连线路径 - 使用更细的线条
    const newEdges = [];
    let previousNodeId = sourceNodeId;
    
    // 连接中间节点
    for (const node of intermediateNodes) {
      const edgeData = {
        source: previousNodeId,
        target: node.id,
        attrs: {
          line: {
            stroke: '#ff6b6b', // 使用红色区分
            strokeWidth: 1, // 更细的线条
            strokeDasharray: '5,5', // 虚线样式
            targetMarker: {
              name: 'block',
              width: 6, // 更小的箭头
              height: 4
            }
          }
        }
      };
      
      const edge = graph.addEdge(edgeData);
      newEdges.push(edge);
      previousNodeId = node.id;
    }
    
    // 连接最后一个中间节点到目标节点
    if (intermediateNodes.length > 0) {
      const finalEdgeData = {
        source: intermediateNodes[intermediateNodes.length - 1].id,
        target: targetNodeId,
        attrs: {
          line: {
            stroke: '#ff6b6b', // 使用红色区分
            strokeWidth: 1, // 更细的线条
            strokeDasharray: '5,5', // 虚线样式
            targetMarker: {
              name: 'block',
              width: 6, // 更小的箭头
              height: 4
            }
          }
        }
      };
      
      const finalEdge = graph.addEdge(finalEdgeData);
      newEdges.push(finalEdge);
    }
    
    // 更新攻击路径
    const sourceName = sourceNode.getAttrByPath('text').text || '未知节点';
    const targetName = targetNode.getAttrByPath('text').text || '未知节点';
    console.log('sourceNode', sourceNode);
    console.log('targetNode', targetNode.getAttrByPath('text').text);
    // 创建新的攻击步骤 - 使用">"表示递进关系
    const pathDescription = [sourceName, ...selectedVulnPath.value, targetName].join(' > ');
    const newStep = {
      from: sourceName,
      to: targetName,
      desc: `通过${pathDescription}路径攻击${targetName}`,
      attack: generateAttackTechnique(sourceNode, targetNode),
      rate: Math.floor(Math.random() * 30) + 70,
      intermediateSteps: selectedVulnPath.value,
      isDetailedPath: true, // 标记为详细路径
      pathDescription: pathDescription // 添加完整路径描述
    };
    
    // 添加到攻击路径
    mockPath.value.steps.push(newStep);
    
    // 重新计算成功率
    const totalRate = mockPath.value.steps.reduce((sum, step) => sum + step.rate, 0);
    mockPath.value.success = Math.round(totalRate / mockPath.value.steps.length);
    
    // 更新战术详情
    updateTacticDetails();
    
    // 清空选择的漏洞路径
    selectedVulnPath.value = [];
    
    ElMessage.success('详细攻击路径生成成功！');
    
  } catch (error) {
    console.error('生成攻击路径失败:', error);
    ElMessage.error('生成攻击路径失败');
  }
};

</script>

<style lang="scss">
.detailBox {
  height: calc(100vh - 84px);
}

.tactical-content {
  display: flex;
  align-items: flex-start;
  justify-content: flex-start;
  height: calc(100% - 50px);
  position: relative;

  .node-info-panel {
    position: absolute;
    top: 0;
    right: 0;
    height: 100%;
    width: 360px;
    // background: #1a2233;
    border-radius: 0 8px 8px 0;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    z-index: 1000;
    border-left: 1.5px solid #2c365a;
    color: #fff;
    display: flex;
    flex-direction: column;
    .panel-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 0 20px;
      background: #202a3c;
      border-radius: 0 8px 0 0;
      border-bottom: 1px solid #2c365a;
      .el-tabs {
        flex: 1;
        .el-tabs__header {
          margin-bottom: 0;
        }
      }
    }
    .panel-content {
      flex: 1;
      overflow-y: auto;
      padding: 18px 20px 0 20px;
      background: #1a2233;
      .info-item, .tactic-info-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 6px 0;
        font-size: 15px;
        border-bottom: 1px solid #232c44;
        label {
          color: #7ed6a5;
          min-width: 80px;
        }
        span {
          color: #fff;
          font-weight: 500;
        }
      }
      /* .vuln-list-title {
        margin: 18px 0 8px 0;
        color: #7ed6a5;
        font-weight: 600;
        font-size: 15px;
        display: flex;
        align-items: center;
        gap: 6px;
      } */
      
      .vuln-tree-section {
        margin: 18px 0 8px 0;
        
        .vuln-tree-title {
          color: #7ed6a5;
          font-weight: 600;
          font-size: 15px;
          display: flex;
          align-items: center;
          justify-content: space-between;
          gap: 6px;
          margin-bottom: 12px;
          
          .el-button {
            font-size: 12px;
            padding: 4px 8px;
            height: 24px;
          }
        }
        
        .selected-path {
          margin-bottom: 15px;
          
          .path-label {
            color: #7ed6a5;
            font-size: 14px;
            margin-bottom: 8px;
          }
          
          .path-items {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 6px;
            
            .el-tag {
              background: #2c365a;
              border-color: #4ade80;
              color: #4ade80;
              font-size: 12px;
            }
            
            .path-separator {
              color: #4ade80;
              font-size: 14px;
              font-weight: bold;
              margin: 0 2px;
            }
          }
        }
        
        .vuln-options {
          .vuln-option {
            display: flex;
            align-items: center;
            padding: 8px 12px;
            background: #2c365a;
            border-radius: 6px;
            margin-bottom: 6px;
            cursor: pointer;
            transition: all 0.2s;
            
            &:hover {
              background: #3c466a;
              transform: translateX(4px);
            }
            
            .el-icon {
              color: #4ade80;
              margin-right: 8px;
              font-size: 14px;
            }
            
            span {
              color: #fff;
              font-size: 14px;
              flex: 1;
            }
            
            .expand-icon {
              color: #7ed6a5;
              margin-left: auto;
              font-size: 12px;
            }
          }
        }
      }
      .vuln-card {
        background: #2c365a;
        border-radius: 8px;
        padding: 12px 16px;
        margin-bottom: 10px;
        .vuln-header {
          display: flex;
          justify-content: space-between;
          align-items: center;
          .vuln-title {
            color: #ff6b81;
            font-weight: 700;
            font-size: 15px;
          }
          .vuln-protocol {
            background: #4ade80;
            color: #222;
            border-radius: 6px;
            padding: 2px 10px;
            font-size: 13px;
            font-weight: 600;
          }
        }
        .vuln-desc {
          color: #fff;
          margin: 6px 0 2px 0;
        }
        .vuln-attack {
          color: #fbbf24;
          font-size: 13px;
        }
        .vuln-rate {
          color: #4ade80;
          font-size: 13px;
          font-weight: 600;
          margin-top: 2px;
        }
      }
      .path-header, .tactic-header {
        display: flex;
        justify-content: space-between;
        align-items: center;
        font-size: 16px;
        font-weight: 600;
        color: #7ed6a5;
        margin-bottom: 10px;
      }
      
      .path-filter {
        margin-bottom: 15px;
        .el-tag {
          background: #2c365a;
          border-color: #4ade80;
          color: #4ade80;
          font-weight: 500;
        }
      }
      
      .path-step {
        background: #232c44;
        border-radius: 8px;
        padding: 10px 14px;
        margin-bottom: 10px;
        
        &.detailed-path {
          background: #2a1f2e;
          border-left: 4px solid #ff6b6b;
        }
        
        .path-step-title {
          color: #fff;
          font-weight: 600;
          display: flex;
          align-items: center;
          flex-wrap: wrap;
          gap: 8px;
        }

        
        .path-intermediate {
          margin: 8px 0;
          
          .intermediate-label {
            color: #ff6b6b;
            font-size: 12px;
            font-weight: 600;
            margin-bottom: 4px;
          }
          
          .intermediate-steps {
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            gap: 8px;
            
            .path-item {
          color: #fff;
              font-size: 12px;
              font-weight: 500;
            }
            
            .path-separator {
              color: #ff6b6b;
          font-size: 14px;
              font-weight: bold;
              margin: 0 4px;
        }
          }
        }
        
        .path-step-attack {
          color: #fbbf24;
          font-size: 13px;
        }
        .path-step-rate {
          color: #4ade80;
          font-size: 13px;
          font-weight: 600;
        }
      }
      
      .path-empty {
        text-align: center;
        padding: 40px 20px;
        color: #7ed6a5;
        .el-empty__description {
          color: #7ed6a5;
        }
      }
      
      .path-footer {
        color: #7ed6a5;
        font-size: 15px;
        margin-top: 10px;
      }
      .tactic-info-item {
        margin-bottom: 8px;
      }
      .tactic-step {
        display: flex;
        align-items: flex-start;
        margin-bottom: 10px;
        .tactic-step-num {
          background: #4ade80;
          color: #222;
          border-radius: 50%;
          width: 28px;
          height: 28px;
          display: flex;
          align-items: center;
          justify-content: center;
          font-weight: 700;
          font-size: 16px;
          margin-right: 10px;
        }
        .tactic-step-content {
          flex: 1;
          .tactic-step-title {
            color: #fff;
            font-weight: 600;
          }
          .tactic-step-desc {
            color: #fff;
            font-size: 14px;
          }
          .tactic-step-attack {
            color: #fbbf24;
            font-size: 13px;
          }
        }
      }
      
      .tactic-empty {
        text-align: center;
        padding: 40px 20px;
        color: #7ed6a5;
        .el-empty__description {
          color: #7ed6a5;
        }
      }
    }
  }

  .tactical-main {
    flex: 1;
    height: 100%;
    width: calc(100% - 405px);

    .graphMain {
      width: 100% !important;
      height: 100% !important;
    }

    .graphMain tspan {
      user-select: none;
    }
  }

  .nodeMenu {
    color: #e1efff;
    padding: 10px;
    background: #094585;
    border-radius: 4px;
    box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.16);
    
    li:hover {
      background-color: #0073f3;
      color: #ffffff;
      cursor: pointer;
    }

    li {
      padding: 5px 20px;
      border-radius: 4px;
      display: flex;
      justify-content: center;
      align-items: center;
      font-size: 11px;

      span {
        margin-left: 5px;
      }
    }
  }

  .x6-graph-scroller {
    z-index: 100;
  }

  .el-drawer__header {
    margin-bottom: 0px;
  }

  .tabContainer {
    width: 100%;
    height: 100%;
    padding: 10px;
    box-sizing: border-box;
  }

  .el-radio-button {
    border-radius: 5px;
    margin: 2px;
  }

  .el-tabs,
  .el-tab-pane {
    height: 100%;
  }

  .el-tabs__content {
    height: calc(100% - 50px) !important;
  }

  // 设置 tabs 项的默认颜色
  .el-tabs__item {
    color: var(--el-color-primary) !important;
  }

  // 左侧设备区域样式
  .leftIcon {
    position: absolute;
    left: 405px; // 默认位置：在左侧区域右边（405px宽度）
    top: 50%;
    transform: translateY(-50%);
    width: 20px;
    height: 60px;
    background: #fff;
    border-radius: 0 8px 8px 0;
    box-shadow: 2px 0 8px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    z-index: 1001;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.3s ease;

    &:hover {
      background: #f0f8ff;
    }

    &.rightIcon {
      left: 0; // 收起时：紧贴左边
    }

    .lefticonimg {
      width: 12px;
      height: 12px;
      background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%235b6775"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>') no-repeat center;
      background-size: contain;
    }

    .righticonimg {
      width: 12px;
      height: 12px;
      background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="%235b6775"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>') no-repeat center;
      background-size: contain;
    }
  }

  .left {
    width: 405px;
    height: 100%;
    background: #ffffff;
    border-right: 1px solid #e0e6ed;
    overflow: hidden;
    transition: all 0.3s ease;
    z-index: 1000;
    margin-right: 10px; // 添加右边距，实现10px间隔

    // 移除 scene-fold-left 样式，使用 display: none 来控制显示隐藏

    .scenes_left {
      background: #ffffff !important;
    }

    .screen_content-component {
      height: auto;
      margin-bottom: 12px;
    }

    .screen_content-bottom {
      height: calc(100% - 140px);
      position: relative;
    }
  }

  .scene-fold {
    // 移除 margin-left，使用 flex 布局自然排列
  }

  .scene-no-fold {
    // 移除 margin-left，使用 flex 布局自然排列
  }

  .resource-pane {
    border-radius: 12px;
    padding: 0;
    min-height: 100%;
    box-shadow: 0 2px 12px rgba(24, 144, 255, 0.04);
  }

  .group-bar {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 10px;
    margin-bottom: 12px;
    
    .group-btn {
      height: 34px;
      line-height: 34px;
      text-align: center;
      border-radius: 8px;
      background: #fff;
      color: #5b6775;
      font-size: 14px;
      cursor: pointer;
      border: 1.5px solid #e0e6ed;
      transition: all 0.18s cubic-bezier(0.4, 0, 0.2, 1);
      user-select: none;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-weight: 500;
      box-shadow: none;
      padding: 0 5px;
      
      &:hover {
        border-color: #1890ff;
        color: #fff;
        background: #1890ff;
        box-shadow: 0 2px 8px rgba(24, 144, 255, 0.1);
      }
      
      &.active {
        background: #1890ff;
        color: #fff;
        border-color: #1890ff;
        font-weight: 600;
        box-shadow: 0 4px 16px rgba(24, 144, 255, 0.16);
      }
    }
  }

  .search-card {
    background: #fff;
    border-radius: 28px;
    box-shadow: 0 1px 8px rgba(24, 144, 255, 0.06);
    margin-bottom: 12px;
    border: none;
    
    .compact-search-input {
      .el-input__wrapper {
        border-radius: 28px;
        border: 2px solid #e0e6ed;
        box-shadow: none;
        transition: border-color 0.2s, box-shadow 0.2s;
        padding-left: 8px;
        min-height: 36px;
        display: flex;
        align-items: center;
      }
      
      .el-input__prefix {
        color: #1890ff;
        margin-right: 4px;
        display: flex;
        align-items: center;
        font-size: 16px;
        
        svg {
          width: 16px;
          height: 16px;
        }
      }
      
      input {
        background: transparent;
        border-radius: 28px;
        color: #5b6775;
        font-size: 14px;
        padding-left: 0;
        
        &::placeholder {
          color: #c2cbe0;
          font-size: 14px;
          letter-spacing: 0.2px;
        }
      }
      
      &.is-focus .el-input__wrapper {
        border-color: #1890ff;
        box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.08);
        background: #fff;
      }
    }
  }

  .resource-collapse {
    background: transparent;
    border-top: none;
    border-bottom: none;
    overflow-y: auto;

    .custom-collapse-item {
      margin-bottom: 18px;
      background: #f7faff;
      border-radius: 14px;
      box-shadow: 0 2px 12px rgba(24, 144, 255, 0.06);
      border: 1.5px solid #e0e6ed;
      overflow: hidden;
      
      .el-collapse-item__header {
        font-weight: 600;
        font-size: 15px;
        color: #5b6775;
        border-left: 3px solid #1890ff;
        background: #f7faff;
        border-radius: 14px 14px 0 0;
        min-height: 40px;
        align-items: center;
        display: flex;
        padding-left: 14px;
        justify-content: space-between;
        
        .el-collapse-item__arrow {
          margin-left: auto !important;
          order: 2;
        }
      }
      
      .el-collapse-item__wrap {
        background: #fff;
        border-radius: 0 0 14px 14px;
      }
      
      .el-collapse-item__content {
        padding: 0 10px 10px;
      }
    }
  }

  .resource-collapse-other {
    height: calc(100vh - 350px);
  }

  .resource-list {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
    padding: 14px 0 6px 0;
  }

  .resource-card {
    width: 100%;
    min-width: 0;
    height: 88px;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(24, 144, 255, 0.06);
    cursor: grab;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    transition: box-shadow 0.2s, border 0.2s;
    border: 1.5px solid #e0e6ed;
    
    &:hover {
      border-color: #1890ff;
      box-shadow: 0 4px 16px rgba(24, 144, 255, 0.12);
    }

    .el-tooltip__trigger {
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
    }

    .icon-bg {
      width: 45px;
      height: 54px;
      background: #fff;
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      box-shadow: 0 1px 4px rgba(24, 144, 255, 0.04);
    }
    
    .resource-icon {
      width: 45px;
      height: 54px;
      object-fit: contain;
    }
    
    .resource-name {
      display: inline-block;
      width: 68px;
      font-size: 14px;
      color: #5b6775;
      text-align: center;
      word-break: break-all;
      line-height: 1;
      max-width: 120px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-weight: 500;
      position: relative;
    }
  }

  // 连线信息弹框样式
  .connection-info {
    .connection-header {
      display: flex;
      align-items: center;
      font-size: 18px;
      font-weight: 600;
      color: #4ade80;
      margin-bottom: 20px;
      padding-bottom: 10px;
      border-bottom: 2px solid #e0e6ed;
    }
    
    .connection-details {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-bottom: 20px;
      
      .node-info-section {
        flex: 1;
        
        h4 {
          margin: 0 0 12px 0;
          color: #5b6775;
          font-size: 16px;
          font-weight: 600;
        }
        
        .node-card {
          display: flex;
          align-items: center;
          background: #f8faff;
          border-radius: 12px;
          padding: 16px;
          border: 2px solid #e0e6ed;
          
          .node-icon {
            width: 48px;
            height: 48px;
            border-radius: 50%;
            display: flex;
            align-items: center;
            justify-content: center;
            margin-right: 12px;
            color: white;
            font-size: 20px;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
          }
          
          .node-details {
            flex: 1;
            
            .node-name {
              font-size: 16px;
              font-weight: 600;
              color: #2c3e50;
              margin-bottom: 4px;
            }
            
            .node-type {
              font-size: 14px;
              color: #7f8c8d;
              margin-bottom: 2px;
            }
            
            .node-id {
              font-size: 12px;
              color: #95a5a6;
              font-family: monospace;
            }
          }
        }
      }
      
      .connection-arrow {
        margin: 0 20px;
        display: flex;
        align-items: center;
      }
    }
    
    .connection-meta {
      background: #f8faff;
      border-radius: 8px;
      padding: 16px;
      border: 1px solid #e0e6ed;
      
      .meta-item {
        display: flex;
        justify-content: space-between;
        align-items: center;
        margin-bottom: 8px;
        
        &:last-child {
          margin-bottom: 0;
        }
        
        label {
          color: #5b6775;
          font-weight: 500;
          font-size: 14px;
        }
        
        span {
          color: #2c3e50;
          font-weight: 600;
          font-size: 14px;
          font-family: monospace;
        }
      }
    }
  }

  // 响应式兼容
  @media (max-width: 1400px) {
    .group-bar {
      grid-template-columns: repeat(4, 1fr);
    }
  }
  
  @media (max-width: 900px) {
    .group-bar {
      grid-template-columns: repeat(4, 1fr);
    }
    
    .resource-card {
      .resource-name {
        font-size: 12px;
        max-width: 60px;
      }
    }
  }
}
</style> 